module ext.typecons;

import std.stdint;
import std.typetuple;

import ext.lang;
import ext.string;
import ext.traits;


static if (!is(size_t == uintmax_t))
{
    private string toString(size_t n) pure nothrow
    {
        return ext.string.toString(cast(uintmax_t) n);
    }
}

// [workaround]
private template _Index(size_t i, T...)
{
    alias T[i] _Index;
}

// [workaround] Bug 2740
private template eval(alias v)
{
    enum eval = v;
}

//debug debug = PRINT_GENERATED_CODE;


// : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : //
// Automatic functions implementor
// : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : //

/***************************************
Automatically implements specified functions.

Params:
  Generator = A template instance which has an inner template
    $(D generateBody(alias traits)), which generates each function body
    as a string. The implementFunctions template instantiates
    $(D generateBody) for every implemented function, passing a
    $(D FunctionTraits) instance on the implemented function to the
    $(D traits) parameter.
--------------------
template Generator(...)
{
    template generateBody(alias traits)
    {
        // Arguments are passed in _p0, _p1, and so on.
        enum string generateBody =
            "return _p0;";
    }
}
--------------------
  Funcs = A tuple of $(D FunctionTraits) instances on the functions to
    implement. The $(D FunctionTraits) instances must have non-empty
    $(D name) property.

Example:
--------------------
// Generator for functions which just return the first argument
template EchoGenerator()
{
    template generateBody(alias traits)
    {
        enum string generateBody = "return _p0;";
    }
}

// List of implemented functions
alias TypeTuple!(
        FunctionTraits!("echo", StorageClass.NONE, Linkage.D,
            FunctionAttribute.NONE, Variadic.NO,
            int, Parameter!(int, StorageClass.NONE))
    ) Funcs;

// implements: int echo(int _p0) { return _p0; }
mixin implementFunctions!(EchoGenerator!(), Funcs);

assert(echo(42) == 42);
--------------------
 */
template implementFunctions(alias Generator, Funcs...)
{
    mixin (implementFunctionsGen!(Generator, "Funcs", Funcs).code);
}

private template implementFunctionsGen(
        alias Generator, string FuncsID, Funcs...)
{
    /*
     * Generate the i-th function
     */
    template generateFunction(size_t i)
    {
        alias Funcs[i] Func;

        //
        enum string FT = eval!(FuncsID ~ "[" ~ toString(i) ~ "]");

        //
        enum string rtype = FT ~ ".ReturnType";

        /*
         * Generate k-th parameter
         */
        template generateParam(size_t k)
        {
            enum string generateParam =
                appendStorageClass(
                    FT ~ ".Parameters[" ~ toString(k) ~ "].Type",
                    Func.Parameters[k].storage) ~
                " _p" ~ toString(k);
        }

        template generateParams(size_t k)
        {
            static if (k < Func.Parameters.length)
            {
                enum string generateParams =
                    (k == 0 ? "" : ", ") ~
                    generateParam!(k) ~
                    generateParams!(k + 1);
            }
            else
            {
                enum string generateParams = "";
            }
        }
        enum params = appendVararg(generateParams!(0), Func.variadic);

        enum sig = prefixFunction(
                rtype ~ " " ~ Func.name ~ "(" ~ params ~ ")",
                Func.convention, Func.storage, Func.attributes);

        enum code =
            sig ~ "\n"
            "{\n"
                ~ Generator.generateBody!(Func) ~ "\n"
            "}";
    }

    // Generate i, i+1, ...-th functions
    template foreachFunction(size_t i)
    {
        static if (i < Funcs.length)
            enum foreachFunction =
                generateFunction!(i).code ~ "\n" ~
                foreachFunction!(i + 1);
        else
            enum foreachFunction = "";
    }

    enum code = foreachFunction!(0);

    debug (PRINT_GENERATED_CODE)
    {
        pragma(msg, "--------------------");
        pragma(msg, code);
        pragma(msg, "--------------------");
    }
}

unittest
{
    struct Scope
    {
        alias      StorageClass S;
        alias           Linkage L;
        alias FunctionAttribute A;
        alias          Variadic V;
        template MyGen()
        {
            template generateBody(alias traits)
            {
                enum string generateBody = "return _p0;";
            }
        }
        mixin implementFunctions!(
                MyGen!(),
                FunctionTraits!(
                    "foo", S.CONST, L.D, A.PURE | A.NOTHROW, V.NO,
                    real, Parameter!(real, S.NONE)),
                FunctionTraits!(
                    "bar", S.NONE, L.C, A.NONE, V.VARIADIC,
                    int, Parameter!(int, S.NONE))
            );
    }
    Scope s;
    static assert(is(typeof(s.foo(42.L)) == real));
    static assert(is(typeof(s.bar(42)) == int));
    static assert(is(typeof(s.bar(42, 1, 2, 3)) == int));
}


// : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : //
// Automatic base class implementor
// : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : //

/*
Returns a $(D FunctionTraits) instance on a function which can override
functions represented by the given $(D FunctionTraits) instances $(D F)
and $(D G). The overrided functions must be return-type covariant.
 */
private template OverridingFunctionTraits(alias F, alias G)
{
    static assert(isCovariantFunctionTraits!(F, G));

    alias FunctionTraits!(
            F.name,
            F.storage,
            F.convention,
            F.attributes | G.attributes,
            F.variadic,
            MostDerivedType!(F.ReturnType, G.ReturnType),
            F.Parameters
        ) OverridingFunctionTraits;
}


/*
Merges $(D FunctionTraits) instances in $(D tail) that are covariant
with the $(D FunctionTraits) instance $(D head) into an
$(D OverridingFunctionTraits) instance.

Returns:
  merge = The $(D OverridingFunctionTraits) instance.
  rest  = $(D FunctionTraits) instances in $(D tail) that were not
          merged.
 */
private template MergeCovariantFunctionTraitsPrim(alias head, tail...)
{
    static if (tail.length)
    {
        static if (isCovariantFunctionTraits!(head, tail[0]))
        {
            alias MergeCovariantFunctionTraitsPrim!(
                    OverridingFunctionTraits!(head, tail[0]),
                    tail[1 .. $]
                ) MergeCovariantFunctionTraitsPrim;
        }
        else
        {
            alias MergeCovariantFunctionTraitsPrim!(head, tail[1 .. $])
                Next;
            alias                     Next.merge merge;
            alias TypeTuple!(tail[0], Next.rest) rest;
        }
    }
    else
    {
        alias head merge;
        alias tail rest; // empty
    }
}

/*
Merges each set of return-type covariant $(D FunctionTraits) instances
in $(D funcs) into an $(D OverridingFunctionTraits) instance.

--------------------
A foo()   \
B foo()   ----->   C foo()
C foo()   /
X foo()   ----->   X foo()
--------------------
 */
private template MergeCovariantFunctionTraits(funcs...)
{
    static if (funcs.length > 0)
    {
        alias TypeTuple!(
                MergeCovariantFunctionTraitsPrim!(funcs).merge,
                MergeCovariantFunctionTraits!(
                    MergeCovariantFunctionTraitsPrim!(funcs).rest)
            ) MergeCovariantFunctionTraits;
    }
    else
    {
        alias funcs MergeCovariantFunctionTraits; // empty
    }
}

unittest
{
    alias StorageClass      S;
    alias Linkage           L;
    alias FunctionAttribute F;
    alias Variadic          V;

    // void* foo() nothrow
    alias FunctionTraits!(
            "foo", S.NONE, L.init, F.NOTHROW, V.NO, void*) FT1;
    // int foo() pure
    alias FunctionTraits!(
            "foo", S.NONE, L.init, F.PURE, V.NO, int) FT2;
    // int* foo()
    alias FunctionTraits!(
            "foo", S.NONE, L.init, F.NONE, V.NO, int*) FT3;
    // int foo()
    alias FunctionTraits!(
            "foo", S.NONE, L.init, F.NONE, V.NO, int) FT4;

    alias MergeCovariantFunctionTraits!(FT1, FT2, FT3, FT4) M;

    static assert(M.length == 2);

    // F1|F3 = int* foo() nothrow
    static assert(M[0].name == "foo");
    static assert(M[0].storage == S.NONE);
    static assert(M[0].convention == L.init);
    static assert(M[0].attributes == F.NOTHROW);
    static assert(M[0].variadic == V.NO);
    static assert(is(M[0].ReturnType == int*));
    static assert(M[0].Parameters.length == 0);

    // F2|F4 = int M2() pure
    static assert(M[1].name == "foo");
    static assert(M[1].storage == S.NONE);
    static assert(M[1].convention == L.init);
    static assert(M[1].attributes == F.PURE);
    static assert(M[1].variadic == V.NO);
    static assert(is(M[1].ReturnType == int));
    static assert(M[1].Parameters.length == 0);
}


/*
Merges each set of return-type covariant functions in the given
overload set $(D os) into a $(D FunctionTraits) instance representing
a function that can override the covariant functions.

Returns:
An $(D OverloadSet) instance consisting of $(D FunctionTraits)
instances on the overriding functions.
 */
private template MergeCovariantOverloads(alias os)
{
    alias OverloadSet!(
            os.name,
            MergeCovariantFunctionTraits!(
                staticMap!(FunctionTraits, os.overloads))
        ) MergeCovariantOverloads;
}

unittest
{
    struct Scope
    {
        interface I { int* foo() const; }
        interface J { void* foo() const; }
        interface K { int foo(int) pure; }
        interface L { int foo(int); }
        alias MergeCovariantOverloads!(
                OverloadSet!("foo", I.foo, J.foo, K.foo, L.foo)) M;
        static assert(M.name == "foo");
        static assert(M.overloads.length == 2);
        alias M.overloads[0] M0;
        static assert(M0.storage == StorageClass.CONST);
        static assert(M0.convention == Linkage.D);
        static assert(M0.attributes == FunctionAttribute.NONE);
        static assert(M0.variadic == Variadic.NO);
        static assert(is(M0.ReturnType == int*));
        static assert(M0.Parameters.length == 0);
        alias M.overloads[1] M1;
        static assert(M1.storage == StorageClass.NONE);
        static assert(M1.convention == Linkage.D);
        static assert(M1.attributes == FunctionAttribute.PURE);
        static assert(M1.variadic == Variadic.NO);
        static assert(is(M1.ReturnType == int));
        static assert(M1.Parameters.length == 1);
        static assert(is(M1.Parameters[0].Type == int));
        static assert(M1.Parameters[0].storage == StorageClass.NONE);
    }
}


/*
Returns a tuple consisting of function overloads extracted from every
$(D OverloadSet) instance in $(D set).
 */
private template CollapseOverloadSet(set...)
{
    alias staticMap!(.CollapseOverloadSet, set) CollapseOverloadSet;
}

private template CollapseOverloadSet(alias set)
{
    alias CollapseOverloadSetImpl!(set.name, set.overloads)
        CollapseOverloadSet;
}

private template CollapseOverloadSetImpl(string name, funcs...)
{
    static if (funcs.length)
        alias TypeTuple!(
                _Index!(0, funcs).rename!(name),
                CollapseOverloadSetImpl!(name, funcs[1 .. $])
            ) CollapseOverloadSetImpl;
    else
        alias TypeTuple!() CollapseOverloadSetImpl;
}

unittest
{
    alias CollapseOverloadSet!(
            OverloadSet!("foo",
                FunctionTraits!("",
                    StorageClass.NONE, Linkage.D,
                    FunctionAttribute.NONE, Variadic.NO,
                    void)),
            OverloadSet!("bar",
                FunctionTraits!("",
                    StorageClass.NONE, Linkage.D,
                    FunctionAttribute.NONE, Variadic.NO,
                    int))
        ) List;
    static assert(List.length == 2);
    static assert(List[0].name == "foo");
    static assert(is(List[0].ReturnType == void));
    static assert(List[1].name == "bar");
    static assert(is(List[1].ReturnType == int));
}


/***************************************
Automatically implements functions that override functions defined in
$(D C) and its ancestors satisfying the predicate $(D pred).
 */
template implementBaseMethods(
        C, alias Generator, alias pred = isAbstractFunction)
{
    mixin implementFunctions!(Generator,
        CollapseOverloadSet!(
            staticMap!(MergeCovariantOverloads,
                MethodOverloadSets!(C, pred))));
}


// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - //

/*
Generates alias declarations which reveal all methods in Base.
 */
private template revealHiddenMethods(string base, Base)
{
    enum string revealHiddenMethods =
        revealHiddenMethodsImpl!(base, methodNames!(Base)).code;
}

private template revealHiddenMethodsImpl(string base, alias mnames)
{
    static if (mnames.length)
        enum string code =
            "alias " ~ base ~ "." ~ mnames[0] ~
                              " " ~ mnames[0] ~ ";\n" ~
            revealHiddenMethodsImpl!(base, mnames[1 .. $]).code;
    else
        enum string code = "";
}


/***************************************
$(D BlackHole!(Base)) is a class which implements all abstract methods
defined in $(D Base) and its ancestors. Each implemented method will
just return the $(D .init) property of the return type of the method.

Example:
--------------------
interface I { int foo(); }
void main()
{
    I i = new BlackHole!I;
    assert(i.foo == 0);
}
--------------------

BUGS:
$(UL
  $(LI DMD: Does not compile with the -w option set.)
)
 */
class BlackHole(Base) : Base
{
    /+override+/ // <- BUG
    mixin implementBaseMethods!(Base, BlackHoleGenerator!(Base));

    /* The generated methods would hide methods defined in Base. This
     * will reveal the hidden methods:
     */
    mixin (revealHiddenMethods!("Base", Base));
}

private template BlackHoleGenerator(Base)
{
    // The generated function just returns .init.
    template generateBody(alias traits)
    {
        static if (is(traits.ReturnType == void))
        {
            enum string generateBody = "";
        }
        else static if (traits.attributes & FunctionAttribute.REF)
        {
            // reference to a dummy variable
            enum string generateBody =
                "static __gshared typeof(return) dummy;\n"
                "return dummy;";
        }
        else
        {
            enum string generateBody =
                "return typeof(return).init;";
        }
    }
}

unittest
{
    struct Scope
    {
        interface X {}
        interface Y : X {}
        interface I { X foo() nothrow; }
        interface J { Y foo() const; }
        class C : I, J
        {
            alias I.foo foo;
            alias J.foo foo;
            abstract ref int foo(lazy short i, int[] a...);
            extern (C) abstract void bar(size_t n, ...);
            int bar() const pure nothrow { return 42; }
        }
    }
    auto bh = new BlackHole!(Scope.C);

    Scope.Y y = (cast(immutable) bh).foo();
    assert(y is null);

    int* p = &bh.foo((assert(0), 1), 2, 3, 4); // lazy eval
    assert(p != null); // a static variable is returned

    bh.bar(3, 2, 1, 0); // argptr-based variadic function
    assert(bh.bar() == 42); // non-abstract method is not overridden
}


/***************************************
$(D WhiteHole!(Base)) is a class which implements all abstract methods
defined in $(D Base) and its ancestors. Each method will just throw an
$(D AssertError) when invoked.

Example:
--------------------
interface I { real foo(); }
void main()
{
    I i = new WhiteHole!I;
    i.foo(); // throws AssertError
}
--------------------

BUGS:
$(UL
  $(LI DMD: Does not compile with the -w option set.)
)
 */
class WhiteHole(Base) : Base
{
    /+override+/ // <- BUG
    mixin implementBaseMethods!(Base, WhiteHoleGenerator!(Base));

    /* The generated methods would hide methods defined in Base. This
     * will reveal the hidden methods:
     */
    mixin (revealHiddenMethods!("Base", Base));
}

private template WhiteHoleGenerator(Base)
{
    // Just throw an AssertError.
    string generateBody(alias traits)()
    {
        enum qname = Base.stringof ~ "." ~ traits.name;
        enum id    = qname ~ Parameters_toString!(traits.Parameters);
        enum msg   = id ~ " is not implemented";

        // Use assert in case of nothrow functions.
        // (assert doesn't violate nothrow)
        return "string emsg = q\"EOS_WhiteHole_\n"
                ~ msg ~
                "\nEOS_WhiteHole_\";\n"
               "assert(0, emsg[0 .. $ - 1]);";
    }
}

unittest
{
    static class B
    {
        abstract void foo();
    }
    auto wh = new WhiteHole!B;
    try { wh.foo(); assert(0); } catch (Error e) { }
}

